﻿/* 
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

using System;
using System.Collections.Generic;
using Lucene.Net.Util;
using Lucene.Net.Extension;
//using MemCached;
using search.tradetuber.com;

using IndexReader = Lucene.Net.Index.IndexReader;

namespace Lucene.Net.Search
{

    /// <summary> A {@link Collector} implementation that collects the top-scoring hits,
    /// returning them as a {@link TopDocs}. This is used by {@link IndexSearcher} to
    /// implement {@link TopDocs}-based search. Hits are sorted by score descending
    /// and then (when the scores are tied) docID ascending. When you create an
    /// instance of this collector you should know in advance whether documents are
    /// going to be collected in doc Id order or not.
    /// 
    /// <p/><b>NOTE</b>: The values {@link Float#NaN} and
    /// {Float#NEGATIVE_INFINITY} are not valid scores.  This
    /// collector will not properly collect hits with such
    /// scores.
    /// </summary>
    public abstract class TopScoreDocCollectorExtension : TopDocsCollectorExtension
    {
        private static long secondsOneDay = 240000L;
        private static long secondsOneWeek = 7L * 240000L;
        private static long secondsOneMonth = 30L * 240000L;
        private static long secondsOneSeason = 4L * 30L * 240000L;
        private static long secondsOneYear = 12L * 30L * 240000L;
        private static long secondsTwoYear = 2L * 12L * 30L * 240000L;
        private static long secondsThreeYear = 3L * 12L * 30L * 240000L;

        private long TodayTime = (long)Convert.ToDouble(DateTime.Now.ToString("yyyyMMdd240000"));//取当前时间最大值
        private long TodayTimeShort = (long)Convert.ToDouble(DateTime.Now.ToString("yyyyMMdd"));//取当前时间最大值
        private Dictionary<int, string> FieldCache = new Dictionary<int, string>();
        private Dictionary<int, string> FieldCacheByTimeB = new Dictionary<int, string>();
        private Dictionary<int, string> FieldCacheByTimeP = new Dictionary<int, string>();
        private Dictionary<int, string> FieldCacheByCreditIndex = new Dictionary<int, string>();
        //private List<string> StandardMemberCompanyIDCache = new List<string>();

        private bool blGroupByExits = false;
        private bool blGroupByTimeBExits = false;
        private bool blGroupByTimePExits = false;

        // Assumes docs are scored in order.
        private class InOrderTopScoreDocCollector : TopScoreDocCollectorExtension
        {
            internal InOrderTopScoreDocCollector(int numHits, IndexSearcherExtension _IndexSearcherExtension)
                : base(numHits, _IndexSearcherExtension)
            {
            }

            public override void Collect(int doc)
            {
                float score = scorer.Score();

                // This collector cannot handle these scores:
                System.Diagnostics.Debug.Assert(score != float.NegativeInfinity);
                System.Diagnostics.Debug.Assert(!float.IsNaN(score));

                totalHits++;

                doc += docBase;

                //当分组的CompanyID不存在时return
                //防止出现Dictionary<companyid="",int>的对应情况出现
                if (blGroupByExits)
                {
                    if (FieldCache.ContainsKey(doc))
                    {
                        if (string.IsNullOrEmpty(FieldCache[doc]))
                            return;
                        if (FieldCacheByCreditIndex.ContainsKey(doc))
                            if (!string.IsNullOrEmpty(FieldCacheByCreditIndex[doc]))
                                if (Convert.ToDecimal(FieldCacheByCreditIndex[doc]) > (decimal)0.50)
                                    score = 101f + score;
                    }
                    else
                        return;
                }

                if (blGroupByTimePExits)//按时间排序
                    score = SortByTime(score, doc);
                else if (blGroupByTimeBExits)
                    score = SortByBuyingleadsWithTime(score, doc);

                if (score <= pqTop.score)
                {
                    if (blGroupByExits)
                    {
                        if (!pq.GroupCount.ContainsKey(FieldCache[doc]))
                            pq.GroupCount.Add(FieldCache[doc], 1);
                        else
                            pq.GroupCount[FieldCache[doc]]++;
                    }

                    // Since docs are returned in-order (i.e., increasing doc Id), a document
                    // with equal score to pqTop.score cannot compete since HitQueue favors
                    // documents with lower doc Ids. Therefore reject those docs too.
                    return;
                }

                pqTop = blGroupByExits ? (ScoreDoc)pq.UpdateTop(FieldCache[doc], new ScoreDoc(doc, score)) : (ScoreDoc)pq.UpdateTop(new ScoreDoc(doc, score));
            }

            public override bool AcceptsDocsOutOfOrder()
            {
                return false;
            }
        }

        // Assumes docs are scored out of order.
        private class OutOfOrderTopScoreDocCollector : TopScoreDocCollectorExtension
        {
            internal OutOfOrderTopScoreDocCollector(int numHits, IndexSearcherExtension _IndexSearcherExtension)
                : base(numHits, _IndexSearcherExtension)
            {
            }

            public override void Collect(int doc)
            {
                float score = scorer.Score();

                // This collector cannot handle NaN
                System.Diagnostics.Debug.Assert(!float.IsNaN(score));

                totalHits++;

                doc += docBase;

                if (blGroupByExits)
                {
                    if (FieldCache.ContainsKey(doc))
                    {
                        if (string.IsNullOrEmpty(FieldCache[doc]))
                            return;
                        if (FieldCacheByCreditIndex.ContainsKey(doc))
                            if (!string.IsNullOrEmpty(FieldCacheByCreditIndex[doc]))
                                if (Convert.ToDecimal(FieldCacheByCreditIndex[doc]) > (decimal)0.50)
                                    score = 101f + score;
                    }
                    else
                        return;
                }

                if (blGroupByTimePExits)//按时间排序
                    score = SortByTime(score, doc);
                else if (blGroupByTimeBExits)
                    score = SortByBuyingleadsWithTime(score, doc);

                if (score < pqTop.score || (score == pqTop.score && doc > pqTop.doc))
                {
                    if (blGroupByExits)
                    {
                        if (!pq.GroupCount.ContainsKey(FieldCache[doc]))
                            pq.GroupCount.Add(FieldCache[doc], 1);
                        else
                            pq.GroupCount[FieldCache[doc]]++;
                    }

                    return;
                }

                pqTop = blGroupByExits ? (ScoreDoc)pq.UpdateTop(FieldCache[doc], new ScoreDoc(doc, score)) : (ScoreDoc)pq.UpdateTop(new ScoreDoc(doc, score));
            }

            public override bool AcceptsDocsOutOfOrder()
            {
                return true;
            }
        }

        /// <summary> Creates a new {@link TopScoreDocCollectorExtension} given the number of hits to
        /// collect and whether documents are scored in order by the input
        /// {@link Scorer} to {@link #SetScorer(Scorer)}.
        /// 
        /// <p/><b>NOTE</b>: The instances returned by this method
        /// pre-allocate a full array of length
        /// <code>numHits</code>, and fill the array with sentinel
        /// objects.
        /// </summary>
        public static TopScoreDocCollectorExtension create(int numHits, bool docsScoredInOrder, IndexSearcherExtension _IndexSearcherExtension)
        {
            if (docsScoredInOrder)
            {
                return new InOrderTopScoreDocCollector(numHits, _IndexSearcherExtension);
            }
            else
            {
                return new OutOfOrderTopScoreDocCollector(numHits, _IndexSearcherExtension);
            }
        }

        internal ScoreDoc pqTop;
        internal int docBase = 0;
        internal Scorer scorer;

        // prevents instantiation
        private TopScoreDocCollectorExtension(int numHits)
            : base(new HitQueueExtension(numHits, true))
        {
            // HitQueue implements getSentinelObject to return a ScoreDoc, so we know
            // that at this point top() is already initialized.
            //pqTop = (ScoreDoc)pq.Top();

            pqTop = (ScoreDoc)pq.GetSentinelObject();
        }

        private TopScoreDocCollectorExtension(int numHits, IndexSearcherExtension _IndexSearcherExtension)
            : this(numHits)
        {
            IndexReader _IndexReader = _IndexSearcherExtension.GetIndexReader();

            if (_IndexSearcherExtension.groupByField == GroupByField.CompanyID)
            {
                blGroupByExits = true;

                this.FieldCache = GroupByFieldCache.GetCompanyIDFieldCache(_IndexReader, _IndexSearcherExtension.termName, _IndexSearcherExtension.termByCacheName);

                //当GroupByCompanyID时，下面两项必须缓存
                this.FieldCacheByCreditIndex = GroupByFieldCache.GetCreditIndexFieldCache(_IndexReader, TermName.CreditIndex, TermCacheName.CreditIndex);
                blGroupByTimePExits = true;
                this.FieldCacheByTimeP = GroupByFieldCache.GetTimeFieldCacheByP(_IndexReader, TermName.ItemCreateTime, TermCacheName.ItemCreateTimeP);
            }
            if (_IndexSearcherExtension.groupByField == GroupByField.NotGroup)
            {
                if (_IndexSearcherExtension.termByCacheName == TermCacheName.ItemCreateTimeB)
                {
                    blGroupByTimeBExits = true;
                    this.FieldCacheByTimeB = GroupByFieldCache.GetTimeFieldCacheByB(_IndexReader, _IndexSearcherExtension.termName, _IndexSearcherExtension.termByCacheName);
                }
                else if (_IndexSearcherExtension.termByCacheName == TermCacheName.ItemCreateTimeP)
                {
                    blGroupByTimePExits = true;
                    this.FieldCacheByTimeP = GroupByFieldCache.GetTimeFieldCacheByP(_IndexReader, _IndexSearcherExtension.termName, _IndexSearcherExtension.termByCacheName);
                }
            }
        }

        public /*protected internal*/ override TopDocs NewTopDocs(ScoreDoc[] results, int start)
        {
            if (results == null)
            {
                return EMPTY_TOPDOCS;
            }

            // We need to compute maxScore in order to set it in TopDocs. If start == 0,
            // it means the largest element is already in results, use its score as
            // maxScore. Otherwise pop everything else, until the largest element is
            // extracted and use its score as maxScore.
            float maxScore = System.Single.NaN;
            if (start == 0)
            {
                maxScore = results[0].score;
            }
            else
            {
                for (int i = pq.Size(); i > 1; i--)
                {
                    pq.Pop();
                }
                maxScore = ((ScoreDoc)pq.Pop()).score;
            }

            return new TopDocs(totalHits, results, maxScore, pq.GetGroupCount());
        }

        public override void SetNextReader(IndexReader reader, int base_Renamed)
        {
            docBase = base_Renamed;
        }

        public override void SetScorer(Scorer scorer)
        {
            this.scorer = scorer;
        }

        /// <summary>
        /// 按时间排序
        /// </summary>
        /// <param name="score"></param>
        /// <param name="doc"></param>
        /// <returns></returns>
        private float SortByTime(float score, int doc)
        {
            long time = FieldCacheByTimeP.ContainsKey(doc) ? (long)Convert.ToDouble(FieldCacheByTimeP[doc]) : 0;
            //if (blGroupByTimeBExits)
            //    time = FieldCacheByTimeB.ContainsKey(doc) ? (long)Convert.ToDouble(FieldCacheByTimeB[doc]) : 0;
            //else if (blGroupByTimePExits)
            //    time = FieldCacheByTimeP.ContainsKey(doc) ? (long)Convert.ToDouble(FieldCacheByTimeP[doc]) : 0;
            //注意，此处当doc对应的time值不存在时并不return
            //否则会减少搜索结果数目
            //副作用为此DOC将会排在搜索结果很后面的位置了
            time = TodayTime - time;
            if (time < secondsOneDay)
                score = score * 1.0f;
            else if (time < secondsOneWeek)
                score = score * 0.8f;
            else if (time < secondsOneMonth)
                score = score * 0.6f;
            else if (time < secondsOneSeason)
                score = score * 0.4f;
            else if (time < secondsOneYear)
                score = score * 0.01f;
            else if (time < secondsTwoYear)
                score = score * 0.05f;
            else if (time < secondsThreeYear)
                score = score * 0.08f;
            else
                score = score * 0.09f;
            return score;
        }

        private float SortByBuyingleadsWithTime(float score, int doc)
        {
            long time = FieldCacheByTimeB.ContainsKey(doc) ? (long)Convert.ToDouble(FieldCacheByTimeB[doc].Substring(0, 8)) : 0;
            //注意，此处当doc对应的time值不存在时并不return
            //否则会减少搜索结果数目
            //副作用为此DOC将会排在搜索结果很后面的位置了
            if (time > 0)
            {
                time = TodayTimeShort - time;
                if (time == 0)
                    time = 1;
                else
                    time = -time;
            }
            else
                time = -90000;
            score = (float)time;
            return score;
        }
    }
}